package com.gxitsky.common.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.lang3.ArrayUtils;

import javax.crypto.Cipher;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Arrays;

/**
 * @author gxing
 * @desc RSAUtil加密工具类
 * @date 2021/12/28
 */
@Slf4j
public class RSAUtil {

    public static String RSA = "RSA";
    public static final String SHA256withRSA = "SHA256withRSA";
    private static final int MAX_ENCRYPT_BLOCK = 64;
    private static final int MAX_DECRYPT_BLOCK = 128;

    /**
     * 随机生成密钥对
     */
    public static RSAKeyPair genKeyPair() {
        // KeyPairGenerator类用于生成公钥和私钥对，基于RSA算法生成对象
        KeyPairGenerator keyPairGen = null;
        try {
            keyPairGen = KeyPairGenerator.getInstance(RSA);
        } catch (NoSuchAlgorithmException e) {
            log.error("获取密要对失败:", e);
            return null;
        }
        // 初始化密钥对生成器，密钥大小为96-1024位
        keyPairGen.initialize(1024, new SecureRandom());
        // 生成一个密钥对，保存在keyPair中
        KeyPair keyPair = keyPairGen.generateKeyPair();
        // 私钥
        RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
        // 公钥
        RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
        // 公钥字符串
        String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
        // 私钥字符串
        String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
        return new RSAKeyPair(publicKeyString, privateKeyString);
    }

    /**
     * 公钥加密
     *
     * @param plainText 明文
     * @param publicKey 公钥
     * @return String
     * @throws Exception Exception
     */
    public static String encryptStr(String plainText, String publicKey) throws Exception {
        return Base64.encodeBase64String(encrypt(plainText, publicKey));
    }

    /**
     * 公钥加密
     *
     * @param plainText 明文
     * @param publicKey 公钥
     * @return String
     * @throws Exception Exception
     */
    public static byte[] encrypt(String plainText, String publicKey) throws Exception {
        return encrypt(plainText.getBytes(StandardCharsets.UTF_8), publicKey);
    }

    /**
     * 公钥加密
     *
     * @param data      数据
     * @param publicKey 公钥, 公钥编码标准X.509
     * @return byte[]
     * @throws Exception Exception
     */
    public static byte[] encrypt(byte[] data, String publicKey) throws Exception {
        //base64编码的公钥
        byte[] decoded = Base64.decodeBase64(publicKey);
        RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance(RSA).generatePublic(new X509EncodedKeySpec(decoded));
        //RSA加密
        Cipher cipher = Cipher.getInstance(RSA);
        cipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
        // 加密时超过117字节就报错,采用分段加密解决
        byte[] resultBytes = null;
        for (int i = 0; i < data.length; i += 64) {
            // 注意要使用2的倍数，否则会出现加密后的内容再解密时为乱码
            byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_ENCRYPT_BLOCK));
            resultBytes = ArrayUtils.addAll(resultBytes, doFinal);
        }
        return resultBytes;
    }

    /**
     * 私钥解密
     *
     * @param cipherText 密文
     * @param privateKey 私钥
     * @return String
     * @throws Exception Exception
     */
    public static String decryptStr(String cipherText, String privateKey) throws Exception {
        byte[] decrypt = decrypt(cipherText, privateKey);
        return new String(decrypt);
    }

    /**
     * 私钥解密
     *
     * @param cipherText 密文
     * @param privateKey 私钥
     * @return byte[]
     * @throws Exception Exception
     */
    public static byte[] decrypt(String cipherText, String privateKey) throws Exception {
        return decrypt(Base64.decodeBase64(cipherText), privateKey);
    }

    /**
     * 私钥解密
     *
     * @param data       数据
     * @param privateKey 私钥,私钥编码标准PKCS#8
     * @return byte[]
     * @throws Exception Exception
     */
    public static byte[] decrypt(byte[] data, String privateKey) throws Exception {
        //base64编码的私钥
        byte[] decoded = Base64.decodeBase64(privateKey);
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) KeyFactory.getInstance(RSA).generatePrivate(new PKCS8EncodedKeySpec(decoded));
        //RSA解密
        Cipher cipher = Cipher.getInstance(RSA);
        cipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
        // 解密时超过128字节就报错。为此采用分段解密的办法来解密
        byte[] resultBytes = null;
        for (int i = 0; i < data.length; i += 128) {
            byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_DECRYPT_BLOCK));
            resultBytes = ArrayUtils.addAll(resultBytes, doFinal);
        }
        return resultBytes;
    }

    /**
     * 私钥签名
     *
     * @param data       数据
     * @param privateKey 私钥
     * @return String
     * @throws Exception Exception
     */
    public static String sign(String data, String privateKey) throws Exception {
        byte[] decoded = Base64.decodeBase64(privateKey);
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) KeyFactory.getInstance(RSA).generatePrivate(new PKCS8EncodedKeySpec(decoded));
        Signature privateSignature = Signature.getInstance(SHA256withRSA);
        privateSignature.initSign(rsaPrivateKey);
        privateSignature.update(data.getBytes(StandardCharsets.UTF_8));
        byte[] signature = privateSignature.sign();
        return Base64.encodeBase64String(signature);
    }

    /**
     * 公钥校验
     *
     * @param data      数据
     * @param sign      签名串
     * @param publicKey 公钥
     * @return boolean
     * @throws Exception Exception
     */
    public static boolean verifySign(String data, String sign, String publicKey) throws Exception {
        byte[] decoded = Base64.decodeBase64(publicKey);
        RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance(RSA).generatePublic(new X509EncodedKeySpec(decoded));
        Signature publicSignature = Signature.getInstance(SHA256withRSA);
        publicSignature.initVerify(rsaPublicKey);
        publicSignature.update(data.getBytes(StandardCharsets.UTF_8));
        byte[] signatureBytes = Base64.decodeBase64(sign);
        return publicSignature.verify(signatureBytes);
    }

    /**
     * 私钥加密
     *
     * @param plainText  明文
     * @param privateKey 私钥
     * @return String
     * @throws Exception Exception
     */
    public static String encryptPriKeyStr(String plainText, String privateKey) throws Exception {
        return Base64.encodeBase64String(encryptPriKey(plainText, privateKey));
    }

    /**
     * 私钥加密
     *
     * @param plainText  明文
     * @param privateKey 私钥
     * @return byte[]
     * @throws Exception Exception
     */
    public static byte[] encryptPriKey(String plainText, String privateKey) throws Exception {
        return encryptPriKey(plainText.getBytes(StandardCharsets.UTF_8), privateKey);
    }

    /**
     * 私钥加密
     *
     * @param data       数据
     * @param privateKey 私钥,私钥编码标准PKCS-8
     * @return byte[]
     * @throws Exception Exception
     */
    public static byte[] encryptPriKey(byte[] data, String privateKey) throws Exception {
        //base64编码的公钥
        byte[] decoded = Base64.decodeBase64(privateKey);
        RSAPrivateKey rsaPrivateKey = (RSAPrivateKey) KeyFactory.getInstance(RSA).generatePrivate(new PKCS8EncodedKeySpec(decoded));
        //RSA加密
        Cipher cipher = Cipher.getInstance(RSA);
        cipher.init(Cipher.ENCRYPT_MODE, rsaPrivateKey);
        // 加密时超过117字节就报错,采用分段加密解决
        byte[] resultBytes = null;
        for (int i = 0; i < data.length; i += 64) {
            // 注意要使用2的倍数，否则会出现加密后的内容再解密时为乱码
            byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_ENCRYPT_BLOCK));
            resultBytes = ArrayUtils.addAll(resultBytes, doFinal);
        }
        return resultBytes;
    }

    /**
     * 公钥解密
     *
     * @param cipherText 密文
     * @param publicKey  公钥
     * @return byte[]
     * @throws Exception Exception
     */
    public static String decryptPubKeyStr(String cipherText, String publicKey) throws Exception {
        byte[] bytes = decryptPubKey(cipherText, publicKey);
        return new String(bytes);
    }

    /**
     * 公钥解密
     *
     * @param cipherText 密文
     * @param publicKey  公钥
     * @return byte[]
     * @throws Exception Exception
     */
    public static byte[] decryptPubKey(String cipherText, String publicKey) throws Exception {
        return decryptPubKey(Base64.decodeBase64(cipherText), publicKey);
    }

    /**
     * 公钥解密
     *
     * @param data      密文
     * @param publicKey 公钥,公钥编码标准X.509
     * @return byte[]
     * @throws Exception Exception
     */
    public static byte[] decryptPubKey(byte[] data, String publicKey) throws Exception {
        //base64编码的私钥
        byte[] decoded = Base64.decodeBase64(publicKey);
        RSAPublicKey rsaPublicKey = (RSAPublicKey) KeyFactory.getInstance(RSA).generatePublic(new X509EncodedKeySpec(decoded));
        //RSA解密
        Cipher cipher = Cipher.getInstance(RSA);
        cipher.init(Cipher.DECRYPT_MODE, rsaPublicKey);
        // 解密时超过128字节就报错。为此采用分段解密的办法来解密
        byte[] resultBytes = null;
        for (int i = 0; i < data.length; i += 128) {
            byte[] doFinal = cipher.doFinal(ArrayUtils.subarray(data, i, i + MAX_DECRYPT_BLOCK));
            resultBytes = ArrayUtils.addAll(resultBytes, doFinal);
        }
        return resultBytes;
    }

    /**
     * RSA算法对文件加密
     *
     * @param file
     * @param saveFileName
     * @param key
     */
    public static void encryptByRSA(File file, String saveFileName, Key key) throws Exception {
        File saveFile = new File(saveFileName);
        if (!saveFile.exists()) {
            saveFile.createNewFile();
        }
        DataOutputStream out = new DataOutputStream(new FileOutputStream(saveFile));
        InputStream in = new FileInputStream(file);
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.ENCRYPT_MODE, key);
        crypt(in, out, cipher);
        in.close();
        out.close();
    }

    /**
     * 对数据块加密
     *
     * @param in
     * @param out
     * @param cipher
     */
    public static void crypt(InputStream in, OutputStream out, Cipher cipher) throws Exception {
        int blockSize = cipher.getBlockSize();
        int outputSize = cipher.getOutputSize(blockSize);
        byte[] inBytes = new byte[blockSize];
        byte[] outBytes = new byte[outputSize];
        int inLength = 0;
        boolean next = true;
        while (next) {
            inLength = in.read(inBytes);
            if (inLength == blockSize) {
                int outLength = cipher.update(inBytes, 0, blockSize, outBytes);
                out.write(outBytes, 0, outLength);
            } else {
                next = false;
            }
        }
        if (inLength > 0) {
            outBytes = cipher.doFinal(inBytes, 0, inLength);
        } else {
            outBytes = cipher.doFinal();
        }
        out.write(outBytes);
    }

    /**
     * AES算法对文件解密 使用二进制数据流
     *
     * @param encryptBytes
     * @param key
     * @return
     * @throws Exception
     */
    public static byte[] decryptByAES(byte[] encryptBytes, Key key) throws GeneralSecurityException, IOException {
        DataInputStream in = new DataInputStream(new ByteArrayInputStream(encryptBytes));
        Cipher cipher = Cipher.getInstance("AES");
        cipher.init(Cipher.DECRYPT_MODE, key);
        byte[] bytes = decrypt(in, cipher);
        in.close();
        return bytes;
    }

    /**
     * 对数据块解密 转换成二进制流
     *
     * @param in
     * @param cipher
     * @return
     * @throws IOException
     * @throws GeneralSecurityException
     */
    public static byte[] decrypt(InputStream in, Cipher cipher) throws IOException, GeneralSecurityException {
        int blockSize = cipher.getBlockSize();
        int outputSize = cipher.getOutputSize(blockSize);
        byte[] inBytes = new byte[blockSize];
        byte[] outBytes = new byte[outputSize];

        byte[] rstBytes = new byte[outputSize];

        int inLength = 0;
        int cpLength = 0;
        boolean next = true;
        while (next) {
            inLength = in.read(inBytes);
            if (inLength == blockSize) {
                int outLength = cipher.update(inBytes, 0, blockSize, outBytes);
                // 扩容
                if (cpLength + outLength > rstBytes.length) {
                    rstBytes = Arrays.copyOf(rstBytes, cpLength + outLength);
                }
                System.arraycopy(outBytes, 0, rstBytes, cpLength, outLength);
                cpLength += outLength;
            } else {
                next = false;
            }
        }

        if (inLength > 0) {
            outBytes = cipher.doFinal(inBytes, 0, inLength);
        } else {
            outBytes = cipher.doFinal();
        }
        // 扩容
        if (cpLength + outBytes.length > rstBytes.length) {
            rstBytes = Arrays.copyOf(rstBytes, cpLength + outBytes.length);
        }
        System.arraycopy(outBytes, 0, rstBytes, cpLength, outBytes.length);
        return rstBytes;
    }

    /**
     * @author gxing
     * @desc RSA公钥私钥对
     * @date 2021/12/29 15:43
     */
    public static class RSAKeyPair {
        private String publicKey;
        private String privateKey;

        public RSAKeyPair() {
        }

        public RSAKeyPair(String publicKey, String privateKey) {
            this.publicKey = publicKey;
            this.privateKey = privateKey;
        }

        public String getPublicKey() {
            return publicKey;
        }

        public void setPublicKey(String publicKey) {
            this.publicKey = publicKey;
        }

        public String getPrivateKey() {
            return privateKey;
        }

        public void setPrivateKey(String privateKey) {
            this.privateKey = privateKey;
        }
    }
}